#!/usr/bin/env python
#Copyright (c) 2011 Isaac Dawson (WBTS Project)
#Permission is hereby granted, free of charge, to any person obtaining a copy 
#of this software and associated documentation files (the "Software"), to deal 
#in the Software without restriction, including without limitation the rights 
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
#copies of the Software, and to permit persons to whom the Software is furnished
#to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
#THE SOFTWARE.
import os
import cgi
import json

from twisted.python import components, log
from twisted.application import service
from twisted.web import resource, server, static, vhost, script, twcgi, xmlrpc
from zope.interface import Interface, implements
from OpenSSL import SSL

from wbts.web import processors, channel
from wbts.web.blade_resources import TestCases, TestCaseLogger
from wbts.web.testing_resources import HeaderSetter
from utils import testing_utils
from utils import mgmt_utils

class IWBTSVirtualHostService(Interface):
    def getVirtualHostList(self):
        """
        Returns a deferred of a list object full of dictionaries with
        the vhost name set to key and the value set to the path.
        """
        return

    def addVirtualHost(self, domain, path):
        """
    Adds the virtual host to our list of hosts for immediate access. Will map
    all of our testing resources and fuzzers on initialization.
    Returns a deferred of 0 for success. A 1 for error will be returned if the
    virtual host could not be added.
        """
        return
    
    def addVirtualHosts(self, virtual_hosts):
        """
    Adds a list of virtual hosts to our service. Takes a tuple of the domain
    name and the path[(name,path),(name,path),...]. Returns a 0 for success and
    a 1 if an error occurs.
        """
    
    def removeVirtualHost(self, domain):
        """
    Removes the virtual host from our list immediately. Returns 0 for success
    and 1 if an error occurs.
        """
        return

    def doesVirtualHostExist(self, domain):
        """
    Checks our list of hosts to see if we have a virtual host defined. Returns
    True if exists, False otherwise.
        """

class WBTSVirtualHostService(service.Service):
    implements(IWBTSVirtualHostService)
    """
    WBTSVirtualHostService - This is our container for all of our vhosts. The
    VirtualHosts have already been configured at this point. This service
    is initialized with those virtualhosts. Then, this service is passed to
    our slightly modified server.Site http factory. The Site factory then
    reads in our vhosts and sets up all the backend junk for dealing with
    requests. Primarily, we only concern ourselves with the WBTSVirtualHosts
    object as that is where all of our configuration data resides.
    """
    def __init__(self, config, mgmt_service):
        self.mConfig = config
        self.mMgmtService = mgmt_service
        self.mMgmtService.mVhostService = self
        self._loadInitialVirtualHosts()
        
    def _loadInitialVirtualHosts(self):
        self.mVhosts = WBTSVirtualHosts(self.mMgmtService)
        self.mVhosts.service = self # set the vhosts service to ourselves
        d = self.mMgmtService.getSettingCfgValues('VirtualHostMappings')
        d.addCallback(self.addVirtualHosts)
        d.addErrback(lambda ret: log.err("Error getting vhost mappings: %s"%ret))
        return d
        
        
    def getVirtualHostList(self):
        """
    Returns a list object full of dictionaries with the vhost name set to
    key and the value set to the path.
        """
        result = {}
        for host,fpath in self.mVhosts.hosts.items():
            result[host] = fpath.path
        return result

    def addVirtualHost(self, domain, path):
        """
    Adds the virtual host to our list of hosts for immediate access. Will map
    all of our testing resources and fuzzers on initialization.
    Returns 0 for success. A 1 for error will be returned if the virtual host
    could not be added.
        """
        return self.mVhosts.addVirtualHost(domain, path)
    
    def addVirtualHosts(self, virtual_hosts):
        """
    Adds virtual hosts to our service. Takes a a dictionary object of the domain
    name and the path {'x.com':'/var/host2/','y.com':'/var/host1/'}.
    Returns a 0 for success and a 1 if an error occurs.
        """        
        return self.mVhosts.addVirtualHosts(virtual_hosts)
    
    def removeVirtualHost(self, domain):
        """
    Removes the virtual host from our list immediately. Returns 0 for success
    and 1 if an error occurs.
        """
        return self.mVhosts.removeVirtualHost(domain)
        
    def doesVirtualHostExist(self, domain):
        if (domain in self.mVhosts.keys()):
            return True
        return False
       
class WBTSVirtualHostFactoryFromService(server.Site):
    """
    This is the server Factory for our virtual host services. We modify the
    HTTPChannel/protocol slightly by overriding it with our own WBTSHTTPChannel
    (see web/channel.py).
    """
    def __init__(self, service):
        self.mVirtualHostService = service
        # set the service factory to ourselves
        self.mVirtualHostService.mVirtualHostFactory = self 
        server.Site.__init__(self, self.mVirtualHostService.mVhosts)
        # We need to use a custom HTTP channel so we can access the 'raw' data.
        self.protocol = channel.WBTSHTTPChannel
        
class WBTSVirtualHosts(vhost.NameVirtualHost):
    """
    This is the container for an individual Virtual Host. We inherit from the
    base twisted vhost.NameVirtualHost. We read in the configuration settings
    and have a reference to the main mMgmtService. We call _initVirtualHosts
    which will loop over our individual virtualhost mappings and map the
    various resources necessary for testing. This includes both file resources
    as well as our custom Resources built for handling specific test case types.
    """
    mTestCaseDir = 'testcases'
    def __init__(self, mgmt_service):
        vhost.NameVirtualHost.__init__(self)
        static.loadMimeTypes(['mime.types'])
        self.mService = None # This will be our vhost service at a later point.
        self.mMgmtService = mgmt_service # our mgmt service has refs to db/dns
        
        d = self.mMgmtService.getSettingCfgValue('WebSettings','shared_root')
        d.addCallback(self.setPaths)
        d.addErrback(lambda ret:
            log.err("error getting shared root directory: %s"%ret))
        
        d = self.mMgmtService.getSettingCfgValues('VirtualHostProcessors')
        d.addCallback(self.setProcessors)
        d.addErrback(lambda ret:
            log.err("Error getting processors: %s"%ret))
    
    def setPaths(self, shared_root):
        self.mSharedPath = shared_root
        self.mTestCasePath = self.mSharedPath + self.mTestCaseDir
        
    def setProcessors(self, processors):
        self.mProcessors = self.mMgmtService.mConfig.resolveProcessors(
            processors
        )
    
    def addVirtualHosts(self, virtual_hosts):
        if (isinstance(virtual_hosts, dict) == False):
            log.err("Error, addVirtualHostList requires a dict.")
            return 1
        
        for domain,path in virtual_hosts.items():
            if (isinstance(domain, str) == False or
                isinstance(path, str) == False):
                log.err("Error, unable to add virtual host domain name "\
                        "or path was not a string.")
                return 1
            log.msg("Adding Virtual Host: %s"%domain)
            self.addVirtualHost(domain, path)
        return 0
    
    def addVirtualHost(self, domain, path):
        if (isinstance(domain, str) == False or
            isinstance(path, str) == False):
            log.err("Domain or path is not a string,"\
                    "can not add to our vhosts.")
            return 1
        if (domain == "" or path == ""):
            log.err("Domain or path is empty, can not add to our vhosts.")
            return 1
        
        if (not mgmt_utils.is_path_valid(path)):
            log.err("Path [%s] is not valid for this system."%path)
            return 1
        
        vhost = static.File(path)
                 
        # Add our processors to the web root.
        self.applyProcessorsToResource(vhost)
    
        # add all the vhost_resources to the vhost.
        self.addTestingResources(vhost);
        
        # our shared directory for all hosts
        self.addSpecialResource(vhost, self.mSharedPath, 'shared')

        # testcases, get your testcases! these are our shared testcase files
        self.addSpecialResource(vhost, self.mTestCasePath, self.mTestCaseDir)

        # map our testcase lister/blade system to the top testcases dir    
        self.addBladeResources(vhost)
        
        # add fuzzing subsystem
        self.addFuzzingSubSystem(vhost)
        
        # Our Default Virtual Host
        if (domain == "default"):
            self.default = vhost
            

        # finally add it to the list of vhosts we'll respond to.
        #print domain
        self.addHost(domain, vhost)
        return 0
    
    def removeVirtualHost(self, domain):
        if (domain not in self.hosts.keys()):
            return 1
        self.removeHost(domain)
        return 0

    def addBladeResources(self, vhost):
        vhost.putChild('cases', TestCases(self.mMgmtService,
                                self.mTestCasePath,
                                self.mTestCaseDir))
        # Add the testcase logger subsystem
        vhost.putChild('tc',TestCaseLogger(self.mMgmtService))
        return
    
    def addFuzzingSubSystem(self, vhost):
        return
    
    def addSpecialResource(self, vhost, path, resource_name):
        """
        Adds the processors and uses the header setter resource when serving
        files.
        """
        special_resource = HeaderSetter(path)
        self.applyProcessorsToResource(special_resource)
        vhost.putChild(resource_name, special_resource)
        return    
    
    def addTestingResources(self, vhost):
        """
        Adds our testing support resources. (This is only for JS/DOM style
        testing). For fuzzer support resources see addFuzzingSubSystem.
        """
        testing_utils.testing_support_resources(vhost, self.mMgmtService)
        return
       
    def applyProcessorsToResource(self, resource):
        """
        Assigns our processors to the resource as well as set it to ignore the
        extension so it will properly execute the file and won't display the
        source.
        """
        resource.processors = self.mProcessors
        # ignore the extensions (so we execute and not drop src)
        for ext in self.mProcessors.keys():
            ret = resource.ignoreExt(ext)
            
class WBTSVirtualHostXR(xmlrpc.XMLRPC):
    def __init__(self, service):
        xmlrpc.XMLRPC.__init__(self)
        self.mService = service
        self.putSubHandler('system', xmlrpc.XMLRPCIntrospection(self))
        
    def xmlrpc_getVirtualHostList(self):
        return self.mService.getVirtualHostList()
    
    def xmlrpc_addVirtualHost(self, domain_name, path):
        return self.mService.addVirtualHost(domain_name, path)
        
    def xmlrpc_addVirtualHosts(self, virtual_hosts):
        return self.mService.addVirtualHosts(virtual_hosts)
        
    def xmlrpc_removeVirtualHost(self, domain_name):
        return self.mService.removeVirtualHost(domain_name)
        